/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.log.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.iwindplus.base.domain.constant.CommonConstant;
import com.iwindplus.base.domain.constant.CommonConstant.DbConstant;
import com.iwindplus.base.domain.constant.CommonConstant.UserConstant;
import com.iwindplus.base.domain.enums.BizCodeEnum;
import com.iwindplus.base.domain.exception.BizException;
import com.iwindplus.base.domain.vo.ResultVO;
import com.iwindplus.base.mongo.service.impl.MongoBaseServiceImpl;
import com.iwindplus.base.redis.manager.SerialNumManager;
import com.iwindplus.base.util.DatesUtil;
import com.iwindplus.base.util.PatternUtil;
import com.iwindplus.log.domain.dto.SmsCaptchaLogDTO;
import com.iwindplus.log.domain.dto.SmsCaptchaLogSearchDTO;
import com.iwindplus.log.domain.dto.SmsSendValidDTO;
import com.iwindplus.log.domain.enums.LogCodeEnum;
import com.iwindplus.log.domain.enums.LogCodePrefixEnum;
import com.iwindplus.log.domain.vo.SmsCaptchaLogPageVO;
import com.iwindplus.log.domain.vo.SmsCaptchaLogVO;
import com.iwindplus.log.server.dal.model.SmsCaptchaLogDO;
import com.iwindplus.log.server.service.SmsCaptchaLogService;
import com.iwindplus.mgt.client.power.UserClient;
import com.iwindplus.mgt.domain.dto.power.UserBaseQueryDTO;
import com.iwindplus.mgt.domain.vo.power.UserInfoVO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import net.dreamlu.mica.core.utils.StringUtil;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 短信验证码日志业务层接口实现类.
 *
 * @author zengdegui
 * @since 2018/9/1
 */
@Service
@CacheConfig(cacheNames = {"smsCaptchaLog"})
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class SmsCaptchaLogServiceImpl extends MongoBaseServiceImpl<SmsCaptchaLogDO>
    implements SmsCaptchaLogService {

    private final UserClient userClient;
    private final SerialNumManager serialNumManager;

    @Override
    public String save(SmsCaptchaLogDTO entity) {
        if (ObjectUtil.isEmpty(entity.getBizNumber())) {
            entity.setBizNumber(StringUtil.getUUID());
        }
        entity.setUsed(false);
        if (ObjectUtil.isEmpty(entity.getBizNumber())) {
            entity.setBizNumber(this.serialNumManager.getSerialNumDate(LogCodePrefixEnum.SMS_PREFIX.getValue()));
        }
        final SmsCaptchaLogDO model = BeanUtil.copyProperties(entity, SmsCaptchaLogDO.class);
        super.save(model);
        entity.setId(model.getId());
        return entity.getId();
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean removeByIds(List<String> ids) {
        final Criteria criteria = Criteria.where(CommonConstant.DbConstant.ID).in(ids);
        final Query query = Query.query(criteria);
        List<SmsCaptchaLogDO> data = super.list(query);
        if (CollUtil.isEmpty(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        super.removeByIds(ids, false);
        return Boolean.TRUE;
    }

    @CacheEvict(allEntries = true)
    @Override
    public boolean removeExpireData() {
        Query query = new Query(Criteria.where("expireTime").lt(LocalDateTime.now()));
        return super.remove(query);
    }

    @Override
    public IPage<SmsCaptchaLogPageVO> page(SmsCaptchaLogSearchDTO entity) {
        final PageDTO<SmsCaptchaLogDO> page = new PageDTO<>(entity.getCurrent(), entity.getSize());
        Criteria criteria = new Criteria();
        if (CharSequenceUtil.isNotBlank(entity.getBizNumber())) {
            criteria.and("bizNumber").is(entity.getBizNumber());
        }
        if (CharSequenceUtil.isNotBlank(entity.getTplCode())) {
            criteria.and("tplCode").is(entity.getTplCode());
        }
        if (Objects.nonNull(entity.getOrgId())) {
            criteria.and("orgId").is(entity.getOrgId());
        }
        if (CharSequenceUtil.isNotBlank((entity.getMobile()))) {
            criteria.and("mobile").regex(PatternUtil.getPatternLike(entity.getMobile()));
        }
        if (CharSequenceUtil.isNotBlank(entity.getJobNumber())) {
            Long userId = GatewayLogServiceImpl.getUserIdByJobNumber(userClient, entity.getJobNumber());
            entity.setUserId(userId);
        } else if (CharSequenceUtil.isNotBlank(entity.getMobile())) {
            Long userId = GatewayLogServiceImpl.getUserIdByMobile(userClient, entity.getMobile());
            entity.setUserId(userId);
        }
        if (Objects.nonNull(entity.getUserId())) {
            criteria.and(CommonConstant.UserConstant.USER_ID).is(entity.getUserId());
        }
        Query query = new Query(criteria);
        List<OrderItem> orders = page.getOrders();
        if (CollUtil.isEmpty(orders)) {
            orders = new ArrayList<>(10);
            OrderItem orderItem = OrderItem.desc("modifiedTime");
            orders.add(orderItem);
            page.setOrders(orders);
        }
        final IPage<SmsCaptchaLogDO> modelPage = super.page(page, query);
        return modelPage.convert(model -> BeanUtil.copyProperties(model, SmsCaptchaLogPageVO.class));
    }

    @Override
    public boolean checkCanSend(SmsSendValidDTO entity) {
        final Long userId = entity.getUserId();
        final Long orgId = entity.getOrgId();
        final LocalDateTime now = LocalDateTime.now();
        Query query = Query.query(Criteria.where(UserConstant.USER_ID).is(userId)
            .and(UserConstant.ORG_ID).is(orgId)
            .and("tplCode").is(entity.getTplCode())
            .and("expireTime").gt(now)
            .and("used").is(Boolean.FALSE));
        boolean data = SqlHelper.retBool(super.count(query));
        if (data) {
            throw new BizException(LogCodeEnum.CAPTCHA_NOT_EXPIRED);
        }

        // 限制每天发送次数.
        final Integer limitCountDay = entity.getLimitCountDay();
        if (Objects.nonNull(limitCountDay)) {
            LocalDateTime beginDate = DatesUtil.getTimesMorning();
            LocalDateTime endDate = DatesUtil.getTimesNight();
            Query recentQuery = Query.query(Criteria.where(UserConstant.USER_ID).is(userId)
                .and(UserConstant.ORG_ID).is(orgId)
                .and("tplCode").is(entity.getTplCode())
                .and(DbConstant.CREATED_TIME).gte(beginDate).lte(endDate));
            long recentCount = super.count(recentQuery);
            if (recentCount >= limitCountDay) {
                throw new BizException(LogCodeEnum.CAPTCHA_LIMIT_DAY, new Object[]{limitCountDay});
            }
        }
        // 限制每小时发送条数.
        final Integer limitCountHour = entity.getLimitCountHour();
        if (Objects.nonNull(limitCountHour)) {
            final LocalDateTime timeAgo = now.minusHours(1);
            Query recentQuery = Query.query(Criteria.where(UserConstant.USER_ID).is(userId)
                .and(UserConstant.ORG_ID).is(orgId)
                .and("tplCode").is(entity.getTplCode())
                .and(DbConstant.CREATED_TIME).gte(timeAgo));
            long recentCount = super.count(recentQuery);
            if (recentCount >= limitCountHour) {
                throw new BizException(LogCodeEnum.CAPTCHA_LIMIT_HOUR, new Object[]{limitCountHour});
            }
        }
        // 限制每分钟发送条数.
        final Integer limitCountMinute = entity.getLimitCountMinute();
        if (Objects.nonNull(limitCountMinute)) {
            LocalDateTime timeAgo = now.minusMinutes(1);
            Query recentQuery = Query.query(Criteria.where(UserConstant.USER_ID).is(userId)
                .and(UserConstant.ORG_ID).is(orgId)
                .and("tplCode").is(entity.getTplCode())
                .and(DbConstant.CREATED_TIME).gte(timeAgo));
            long recentCount = super.count(recentQuery);
            if (recentCount >= limitCountMinute) {
                throw new BizException(LogCodeEnum.CAPTCHA_LIMIT_MINUTE, new Object[]{limitCountMinute});
            }
        }
        return Boolean.TRUE;
    }

    @Cacheable(key = "#root.methodName + '_' + #p0", condition = "#p0 != null", unless = "#result == null")
    @Override
    public SmsCaptchaLogVO getDetail(String id) {
        SmsCaptchaLogDO data = super.getById(id);
        if (Objects.isNull(data)) {
            throw new BizException(BizCodeEnum.DATA_NOT_EXIST);
        }
        return BeanUtil.copyProperties(data, SmsCaptchaLogVO.class);
    }

    @Override
    public boolean validate(String tplCode, String mobile, String captcha) {
        final UserBaseQueryDTO entity = UserBaseQueryDTO.builder().mobile(mobile).build();
        final ResultVO<UserInfoVO> userResponse = this.userClient.getLoginInfoByCondition(entity);
        userResponse.errorThrow();
        final UserInfoVO user = userResponse.getBizData();
        final Long userId = user.getUserId();
        final Long orgId = user.getOrgId();

        return this.validateByUserId(tplCode, userId, orgId, captcha);
    }

    @Override
    public boolean validateByUserId(String tplCode, Long userId, Long orgId, String captcha) {
        Query query = Query.query(Criteria.where(UserConstant.USER_ID).is(userId)
                .and(UserConstant.ORG_ID).is(orgId)
                .and("captcha").is(captcha.trim())
                .and("tplCode").is(tplCode))
            .with(Sort.by(Sort.Direction.DESC, CommonConstant.DbConstant.MODIFIED_TIME))
            .limit(1);
        return this.checkCaptcha(query);
    }

    private boolean checkCaptcha(Query query) {
        SmsCaptchaLogDO data = super.getOne(query);
        if (Objects.isNull(data)) {
            throw new BizException(LogCodeEnum.CAPTCHA_ERROR);
        }
        LocalDateTime now = LocalDateTime.now();
        if (now.isAfter(data.getExpireTime())) {
            throw new BizException(LogCodeEnum.CAPTCHA_EXPIRED);
        }
        if (Boolean.TRUE.equals(data.getUsed())) {
            throw new BizException(LogCodeEnum.CAPTCHA_CAN_USE_ONCE);
        }
        SmsCaptchaLogDO build = new SmsCaptchaLogDO();
        build.setId(data.getId());
        build.setUsed(true);
        build.setUseTime(LocalDateTime.now());
        build.setVersion(data.getVersion());
        super.updateById(build);
        return Boolean.TRUE;
    }
}
